package com.jfirer.validator.descriptor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.validation.Constraint;
import javax.validation.ConstraintTarget;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.OverridesAttribute;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.ValidationException;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import javax.validation.groups.Default;
import javax.validation.metadata.ConstraintDescriptor;
import com.jfirer.validator.constraint.validator.AssertFalseValidator;
import com.jfirer.validator.constraint.validator.DecimalMaxValidator;
import com.jfirer.validator.constraint.validator.DecimalMinValidator;
import com.jfirer.validator.constraint.validator.DigitsValidator;
import com.jfirer.validator.constraint.validator.FutureValidator;
import com.jfirer.validator.constraint.validator.MaxValidator;
import com.jfirer.validator.constraint.validator.MinValidator;
import com.jfirer.validator.constraint.validator.NotNullValidator;
import com.jfirer.validator.constraint.validator.NullValidator;
import com.jfirer.validator.constraint.validator.PastValidator;
import com.jfirer.validator.constraint.validator.PatternValidator;
import com.jfirer.validator.constraint.validator.SizeValidator;
import com.jfirer.validator.util.Commons;
import com.jfirer.baseutil.StringUtil;

public class ConstraintDescriptorImpl<T extends Annotation> implements ConstraintDescriptor<T>
{
	private T											annotation;
	private String										messageTemplate;
	private Set<Class<?>>								groups;
	private Set<Class<? extends Payload>>				payload;
	private ConstraintTarget							validationAppliesTo;
	@SuppressWarnings("rawtypes")
	private List										constraintValidatorClasses;
	private Map<String, Object>							attributes;
	private Set<ConstraintDescriptor<?>>				composingConstraints;
	private boolean										reportAsSingleViolation;
	private ConstraintValidator<Annotation, Object>[]	constraintValidators;
	
	@SuppressWarnings("unchecked")
	public ConstraintDescriptorImpl(T annotation, ConstraintValidatorFactory constraintValidatorFactory)
	{
		if (annotation.annotationType().isAnnotationPresent(Constraint.class) == false)
		{
			ValidationException exception = new ValidationException(StringUtil.format("{}没有约束注解", annotation.annotationType().getName()));
			throw exception;
		}
		this.annotation = annotation;
		reportAsSingleViolation = annotation.annotationType().isAnnotationPresent(ReportAsSingleViolation.class);
		attributes = Commons.getAttributes(annotation);
		if (attributes.containsKey("message") == false)
		{
			ValidationException exception = new ValidationException(StringUtil.format("{}缺少message属性", annotation.annotationType().getName()));
			throw exception;
		}
		messageTemplate = (String) attributes.get("message");
		if (attributes.containsKey("groups") == false)
		{
			ValidationException exception = new ValidationException(StringUtil.format("{}缺少groups属性", annotation.annotationType().getName()));
			throw exception;
		}
		groups = (Set<Class<?>>) Commons.arrayToSet((Class<?>[]) attributes.get("groups"));
		if (groups.isEmpty())
		{
			groups.add(Default.class);
		}
		if (attributes.containsKey("validationAppliesTo"))
		{
			this.validationAppliesTo = (ConstraintTarget) attributes.get("validationAppliesTo");
		}
		else
		{
			validationAppliesTo = ConstraintTarget.IMPLICIT;
		}
		constraintValidatorClasses = new ArrayList<Class<? extends ConstraintValidator<T, ?>>>();
		if (annotation instanceof Null)
		{
			constraintValidatorClasses.add(NullValidator.class);
		}
		else if (annotation instanceof NotNull)
		{
			constraintValidatorClasses.add(NotNullValidator.class);
		}
		else if (annotation instanceof AssertFalse)
		{
			constraintValidatorClasses.add(AssertFalseValidator.class);
		}
		else if (annotation instanceof DecimalMax)
		{
			constraintValidatorClasses.add(DecimalMaxValidator.class);
		}
		else if (annotation instanceof DecimalMin)
		{
			constraintValidatorClasses.add(DecimalMinValidator.class);
		}
		else if (annotation instanceof Digits)
		{
			constraintValidatorClasses.add(DigitsValidator.class);
		}
		else if (annotation instanceof Future)
		{
			constraintValidatorClasses.add(FutureValidator.class);
		}
		else if (annotation instanceof Max)
		{
			constraintValidatorClasses.add(MaxValidator.class);
		}
		else if (annotation instanceof Min)
		{
			constraintValidatorClasses.add(MinValidator.class);
		}
		else if (annotation instanceof Past)
		{
			constraintValidatorClasses.add(PastValidator.class);
		}
		else if (annotation instanceof Pattern)
		{
			constraintValidatorClasses.add(PatternValidator.class);
		}
		else if (annotation instanceof Size)
		{
			constraintValidatorClasses.add(SizeValidator.class);
		}
		else
		{
			for (Class<? extends ConstraintValidator<?, ?>> each : annotation.annotationType().getAnnotation(Constraint.class).validatedBy())
			{
				constraintValidatorClasses.add(each);
			}
		}
		constraintValidators = new ConstraintValidator[constraintValidatorClasses.size()];
		for (int i = 0; i < constraintValidatorClasses.size(); i++)
		{
			Class<? extends ConstraintValidator<Annotation, Object>> ckass = (Class<? extends ConstraintValidator<Annotation, Object>>) constraintValidatorClasses.get(i);
			ConstraintValidator<Annotation, Object> validator;
			try
			{
				validator = ckass.newInstance();
				validator.initialize(annotation);
				constraintValidators[i] = validator;
			}
			catch (Throwable e)
			{
				throw new ValidationException(e);
			}
		}
		composingConstraints = defineComposingConstraints(annotation, constraintValidatorFactory);
	}
	
	private Set<ConstraintDescriptor<?>> defineComposingConstraints(T annotation, ConstraintValidatorFactory constraintValidatorFactory)
	{
		Map<OverridesAttribute, Object> overridesAttributes = new HashMap<OverridesAttribute, Object>();
		for (Method each : annotation.annotationType().getMethods())
		{
			if (Modifier.isStatic(each.getModifiers()) || each.getParameterTypes().length > 0)
			{
				continue;
			}
			if (each.isAnnotationPresent(OverridesAttribute.class))
			{
				try
				{
					each.setAccessible(true);
					OverridesAttribute overridesAttribute = each.getAnnotation(OverridesAttribute.class);
					overridesAttributes.put(overridesAttribute, each.invoke(annotation));
				}
				catch (Exception e)
				{
					throw new ValidationException(e);
				}
			}
			else if (each.isAnnotationPresent(OverridesAttribute.List.class))
			{
				try
				{
					each.setAccessible(true);
					for (OverridesAttribute overridesAttribute : each.getAnnotation(javax.validation.OverridesAttribute.List.class).value())
					{
						overridesAttributes.put(overridesAttribute, each.invoke(annotation));
					}
				}
				catch (Exception e)
				{
					throw new ValidationException(e);
				}
			}
		}
		Set<ConstraintDescriptor<?>> composingConstraints = new HashSet<ConstraintDescriptor<?>>();
		for (Annotation each : annotation.annotationType().getAnnotations())
		{
			if (Commons.isConstraintAnnotation(each))
			{
				Map<OverridesAttribute, Object> inheritedOverridesAttributes = new HashMap<OverridesAttribute, Object>();
				for (Entry<OverridesAttribute, Object> entry : overridesAttributes.entrySet())
				{
					if (entry.getKey().constraint() == each.annotationType())
					{
						inheritedOverridesAttributes.put(entry.getKey(), entry.getValue());
					}
				}
				composingConstraints.add(new ConstraintDescriptorImpl<Annotation>(each, groups, inheritedOverridesAttributes, constraintValidatorFactory));
			}
			else if (Commons.isMutliValueConstraintAnnotation(each))
			{
				Annotation[] constraintAnnotations = Commons.getMutliValueConstraintAnnotation(each);
				for (int i = 0; i < constraintAnnotations.length; i++)
				{
					Annotation constraintAnnotation = constraintAnnotations[i];
					Map<OverridesAttribute, Object> inheritedOverridesAttributes = new HashMap<OverridesAttribute, Object>();
					for (Entry<OverridesAttribute, Object> entry : overridesAttributes.entrySet())
					{
						if (entry.getKey().constraint() == constraintAnnotation.annotationType() || entry.getKey().constraintIndex() == i)
						{
							inheritedOverridesAttributes.put(entry.getKey(), entry.getValue());
						}
					}
					composingConstraints.add(new ConstraintDescriptorImpl<Annotation>(each, groups, inheritedOverridesAttributes, constraintValidatorFactory));
				}
			}
		}
		return Collections.unmodifiableSet(composingConstraints);
	}
	
	@SuppressWarnings("unchecked")
	public ConstraintDescriptorImpl(T annotation, Set<Class<?>> inheritedGroups, Map<OverridesAttribute, Object> overridesAttributes, ConstraintValidatorFactory constraintValidatorFactory)
	{
		if (annotation.annotationType().isAnnotationPresent(Constraint.class) == false)
		{
			ValidationException exception = new ValidationException(StringUtil.format("{}没有约束注解", annotation.annotationType().getName()));
			throw exception;
		}
		this.annotation = annotation;
		reportAsSingleViolation = annotation.annotationType().isAnnotationPresent(ReportAsSingleViolation.class);
		attributes = Commons.getAttributes(annotation);
		for (Entry<OverridesAttribute, Object> entry : overridesAttributes.entrySet())
		{
			attributes.put(entry.getKey().name(), entry.getValue());
		}
		if (attributes.containsKey("message") == false)
		{
			ValidationException exception = new ValidationException(StringUtil.format("{}缺少message属性", annotation.annotationType().getName()));
			throw exception;
		}
		messageTemplate = (String) attributes.get("message");
		groups = inheritedGroups;
		if (attributes.containsKey("validationAppliesTo"))
		{
			this.validationAppliesTo = (ConstraintTarget) attributes.get("validationAppliesTo");
		}
		else
		{
			validationAppliesTo = ConstraintTarget.IMPLICIT;
		}
		composingConstraints = new HashSet<ConstraintDescriptor<?>>();
		constraintValidatorClasses = new ArrayList<Class<? extends ConstraintValidator<T, ?>>>();
		if (annotation instanceof Null)
		{
			constraintValidatorClasses.add(NullValidator.class);
		}
		else if (annotation instanceof NotNull)
		{
			constraintValidatorClasses.add(NotNullValidator.class);
		}
		else if (annotation instanceof AssertFalse)
		{
			constraintValidatorClasses.add(AssertFalseValidator.class);
		}
		else if (annotation instanceof DecimalMax)
		{
			constraintValidatorClasses.add(DecimalMaxValidator.class);
		}
		else if (annotation instanceof DecimalMin)
		{
			constraintValidatorClasses.add(DecimalMinValidator.class);
		}
		else if (annotation instanceof Digits)
		{
			constraintValidatorClasses.add(DigitsValidator.class);
		}
		else if (annotation instanceof Future)
		{
			constraintValidatorClasses.add(FutureValidator.class);
		}
		else if (annotation instanceof Max)
		{
			constraintValidatorClasses.add(MaxValidator.class);
		}
		else if (annotation instanceof Min)
		{
			constraintValidatorClasses.add(MinValidator.class);
		}
		else if (annotation instanceof Past)
		{
			constraintValidatorClasses.add(PastValidator.class);
		}
		else if (annotation instanceof Pattern)
		{
			constraintValidatorClasses.add(PatternValidator.class);
		}
		else if (annotation instanceof Size)
		{
			constraintValidatorClasses.add(SizeValidator.class);
		}
		else
		{
			for (Class<? extends ConstraintValidator<?, ?>> each : annotation.annotationType().getAnnotation(Constraint.class).validatedBy())
			{
				constraintValidatorClasses.add(each);
			}
		}
		constraintValidators = new ConstraintValidator[constraintValidatorClasses.size()];
		for (int i = 0; i < constraintValidatorClasses.size(); i++)
		{
			Class<? extends ConstraintValidator<Annotation, Object>> ckass = (Class<? extends ConstraintValidator<Annotation, Object>>) constraintValidatorClasses.get(i);
			ConstraintValidator<Annotation, Object> validator;
			try
			{
				validator = ckass.newInstance();
				validator.initialize(annotation);
				constraintValidators[i] = validator;
			}
			catch (Throwable e)
			{
				throw new ValidationException(e);
			}
		}
		
	}
	
	@Override
	public T getAnnotation()
	{
		return annotation;
	}
	
	@Override
	public String getMessageTemplate()
	{
		return messageTemplate;
	}
	
	@Override
	public Set<Class<?>> getGroups()
	{
		return groups;
	}
	
	@Override
	public Set<Class<? extends Payload>> getPayload()
	{
		return payload;
	}
	
	@Override
	public ConstraintTarget getValidationAppliesTo()
	{
		return validationAppliesTo;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<Class<? extends ConstraintValidator<T, ?>>> getConstraintValidatorClasses()
	{
		return (List<Class<? extends ConstraintValidator<T, ?>>>) constraintValidatorClasses;
	}
	
	@Override
	public Map<String, Object> getAttributes()
	{
		return attributes;
	}
	
	@Override
	public Set<ConstraintDescriptor<?>> getComposingConstraints()
	{
		return composingConstraints;
	}
	
	@Override
	public boolean isReportAsSingleViolation()
	{
		return reportAsSingleViolation;
	}
	
	public ConstraintValidator<Annotation, Object>[] getConstraintValidators()
	{
		return constraintValidators;
	}
}
